package com.rent.common.utils;

import cn.hutool.core.convert.Convert;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.rent.common.constants.ConstantsBase;
import com.rent.common.api.StrPool;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author zhang
 * @Create 2023/7/29 14:58
 */
@Slf4j
@NoArgsConstructor
public final class ContextUtil {

    // 使用 TtlExecutors.getTtlExecutorService 对线程池进行包装，就可以不使用 TtlRunnable
    private static final ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newSingleThreadExecutor());
    /**
     * 支持多线程传递参数
     */
    private static final ThreadLocal<Map<String, String>> THREAD_LOCAL = new TransmittableThreadLocal<Map<String, String>>() {
        /**
         * 如果使用的是TtlExecutors装饰的线程池或者TtlRunnable、TtlCallable装饰的任务
         * 重写copy方法且重新赋值给新的LinkedHashMap，不然会导致父子线程都是持有同一个引用，只要有修改取值都会变化。引用值线程不安全
         * parentValue是父线程执行子任务那个时刻的快照值，后续父线程再次set值也不会影响子线程get，因为已经不是同一个引用
         */
        @Override
        public Map<String, String> copy(Map<String, String> parentValue) {
//            log.info("copy");
            return new LinkedHashMap<>(parentValue);
        }

        /**
         * 如果使用普通线程池执行异步任务，重写childValue即可实现子线程获取的是父线程执行任务那个时刻的快照值，重新赋值给新的LinkedHashMap，父线程修改不会影响子线程（非共享）
         * 但是如果使用的是TtlExecutors装饰的线程池或者TtlRunnable、TtlCallable装饰的任务，此时就会变成引用共享，必须得重写copy方法才能实现非共享
         */
        @Override
        protected Map<String, String> childValue(Map<String, String> parentValue) {
//            log.info("childValue");
            return new LinkedHashMap<>(parentValue);

        }

        /**
         * 初始化,每次get时都会进行初始化
         */
        @Override
        protected Map<String, String> initialValue() {
            log.info("initialValue");
            return new LinkedHashMap<>();
        }
    };


    public static void set(String key, Object value) {
        assert executorService != null;
        executorService.execute(ContextUtil::getLocalMap);
        Map<String, String> map = getLocalMap();
        map.put(key, value == null ? StrPool.EMPTY : value.toString());
    }

    public static void setTenant(String tenant) {
        set(ConstantsBase.JWT_KEY_TENANT, tenant);
    }

    public static void setUsername(String username) {
        set(ConstantsBase.KEY_USER_NAME, username);
    }

    public static <T> T get(String key, Class<T> type) {
        Map<String, String> map = getLocalMap();
        return Convert.convert(type, map.get(key));
    }

    public static <T> T get(String key, Class<T> type, Object def) {
        Map<String, String> map = getLocalMap();
        return Convert.convert(type, map.getOrDefault(key, String.valueOf(def == null ? StrPool.EMPTY : def)));
    }

    public static String get(String key) {
        Map<String, String> map = getLocalMap();
        return map.getOrDefault(key, StrPool.EMPTY);
    }

    public static Map<String, String> getLocalMap() {
        return THREAD_LOCAL.get();
    }


    public static Integer getAccountId() {
        return ContextUtil.get(ConstantsBase.KEY_USER_ID, Integer.class, 0);
    }

    public static String getUsername() {
        return ContextUtil.get(ConstantsBase.KEY_USER_NAME);
    }

    public static String getTenant() {
        return get(ConstantsBase.JWT_KEY_TENANT, String.class, StrPool.EMPTY);
    }
}
